package cn.sylinx.hbatis.ext.proxy.command;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.sylinx.hbatis.db.common.Page;
import cn.sylinx.hbatis.db.common.Record;
import cn.sylinx.hbatis.exception.HbatisException;
import cn.sylinx.hbatis.ext.proxy.annotation.Resource;
import cn.sylinx.hbatis.ext.proxy.annotation.ResourceType;
import cn.sylinx.hbatis.ext.proxy.invoker.BeanListCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.BeanPageCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.BeanQueryOneCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.CommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.ExecuteCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.ObjectListCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.ObjectQueryOneCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.RecordListCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.RecordPageCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.RecordQueryOneCommandInvoker;
import cn.sylinx.hbatis.ext.proxy.invoker.UpdateCommandInvoker;
import cn.sylinx.hbatis.kit.Tuple;

class CommandInvokeHandler implements CommandInvoker {

	private static Set<Class<?>> primaryOrMappedWrapTypeOrFrequentType = new HashSet<>(8);
	static {
		primaryOrMappedWrapTypeOrFrequentType.add(Boolean.class);
		primaryOrMappedWrapTypeOrFrequentType.add(boolean.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Byte.class);
		primaryOrMappedWrapTypeOrFrequentType.add(byte.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Character.class);
		primaryOrMappedWrapTypeOrFrequentType.add(char.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Short.class);
		primaryOrMappedWrapTypeOrFrequentType.add(short.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Integer.class);
		primaryOrMappedWrapTypeOrFrequentType.add(int.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Long.class);
		primaryOrMappedWrapTypeOrFrequentType.add(long.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Float.class);
		primaryOrMappedWrapTypeOrFrequentType.add(float.class);

		primaryOrMappedWrapTypeOrFrequentType.add(Double.class);
		primaryOrMappedWrapTypeOrFrequentType.add(double.class);

		primaryOrMappedWrapTypeOrFrequentType.add(String.class);
		primaryOrMappedWrapTypeOrFrequentType.add(BigInteger.class);
		primaryOrMappedWrapTypeOrFrequentType.add(BigDecimal.class);
		primaryOrMappedWrapTypeOrFrequentType.add(Date.class);
	}

	private MethodMetadata metadata;
	private Tuple tuple;

	CommandInvokeHandler(MethodMetadata metadata, Tuple tuple) {
		this.metadata = metadata;
		this.tuple = tuple;
	}

	/**
	 * 是否是 cn.sylinx.hbatis.db.common.Record 类型
	 * 
	 * @return
	 */
	private boolean isRecordForTruelyReturnType(Class<?> truelyReturnType) {
		return truelyReturnType == Record.class;
	}

	/**
	 * 是否是8种原始类型或者对应的封装类型或者是常用类型
	 * 
	 * @return
	 */
	private boolean isPrimaryOrMappedWrapTypeOrFrequentType(Class<?> truelyReturnType) {
		return primaryOrMappedWrapTypeOrFrequentType.contains(truelyReturnType);
	}

	private CommandInvoker getCommandInvoker() {

		List<Creator> creatorList = new ArrayList<>();
		// 以下需按顺序添加
		creatorList.add(new UpdateCommandInvokerCreator());
		creatorList.add(new ExecuteCommandInvokerCreator());
		creatorList.add(new RecordListCommandInvokerCreator());
		creatorList.add(new ObjectListCommandInvokerCreator());
		creatorList.add(new BeanListCommandInvokerCreator());
		creatorList.add(new RecordPageCommandInvokerCreator());
		creatorList.add(new BeanPageCommandInvokerCreator());
		creatorList.add(new ObjectQueryOneCommandInvokerCreator());
		creatorList.add(new RecordQueryOneCommandInvokerCreator());
		creatorList.add(new BeanQueryOneCommandInvokerCreator());

		for (Creator creator : creatorList) {
			CommandInvoker commandInvoker = creator.create();
			if (commandInvoker != null) {
				return commandInvoker;
			}
		}

		throw new HbatisException("no suitable command invoker");
	}

	public Object invoke() {

		CommandInvoker invoker = getCommandInvoker();
		try {
			return invoker.invoke();
		} finally {
			invoker.releaseResource();
			invoker = null;
		}
	}

	@Override
	public void releaseResource() {
		tuple = null;
		metadata = null;
	}

	private static interface Creator {
		CommandInvoker create();
	}

	private class UpdateCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			boolean isUpdate = resource.resourceType() == ResourceType.UPDATE;
			if (isUpdate) {
				return new UpdateCommandInvoker(truelyDatasource, resource.sql(), params, resource.nativeSql());
			}
			return null;
		}

	}

	private class ExecuteCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			boolean isExecute = resource.resourceType() == ResourceType.EXECUTE;
			if (isExecute) {
				return new ExecuteCommandInvoker(truelyDatasource, resource.sql(), params, resource.nativeSql());
			}
			return null;
		}

	}

	private class RecordListCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> truelyReturnType = metadata.getTruelyReturnType();
			Class<?> returnType = metadata.getReturnType();

			if (returnType == List.class && isRecordForTruelyReturnType(truelyReturnType)) {
				return new RecordListCommandInvoker(truelyDatasource, resource.sql(), params, truelyReturnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}
	}

	private class ObjectListCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> truelyReturnType = metadata.getTruelyReturnType();
			Class<?> returnType = metadata.getReturnType();

			if (returnType == List.class && isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType)) {
				return new ObjectListCommandInvoker(truelyDatasource, resource.sql(), params, truelyReturnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}
	}

	private class BeanListCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> truelyReturnType = metadata.getTruelyReturnType();
			Class<?> returnType = metadata.getReturnType();

			if (returnType == List.class && (!isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType))
					&& (!isRecordForTruelyReturnType(truelyReturnType))) {
				return new BeanListCommandInvoker(truelyDatasource, resource.sql(), params, truelyReturnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}

	}

	private class RecordPageCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> truelyReturnType = metadata.getTruelyReturnType();
			Class<?> returnType = metadata.getReturnType();
			int pageNumber = tuple.getObject(1);
			int pageSize = tuple.getObject(2);

			if (returnType == Page.class && isRecordForTruelyReturnType(truelyReturnType)) {
				return new RecordPageCommandInvoker(truelyDatasource, resource.sql(), params, pageNumber, pageSize,
						truelyReturnType, resource.nativeSql());
			}

			return null;
		}
	}

	private class BeanPageCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> truelyReturnType = metadata.getTruelyReturnType();
			Class<?> returnType = metadata.getReturnType();
			int pageNumber = tuple.getObject(1);
			int pageSize = tuple.getObject(2);

			if (returnType == Page.class && !isRecordForTruelyReturnType(truelyReturnType)) {
				return new BeanPageCommandInvoker(truelyDatasource, resource.sql(), params, pageNumber, pageSize,
						truelyReturnType, resource.nativeSql());
			}

			return null;
		}
	}

	private class ObjectQueryOneCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> returnType = metadata.getReturnType();

			if (isPrimaryOrMappedWrapTypeOrFrequentType(returnType)) {
				return new ObjectQueryOneCommandInvoker(truelyDatasource, resource.sql(), params, returnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}
	}

	private class RecordQueryOneCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> returnType = metadata.getReturnType();

			if (isRecordForTruelyReturnType(returnType)) {
				return new RecordQueryOneCommandInvoker(truelyDatasource, resource.sql(), params, returnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}
	}

	private class BeanQueryOneCommandInvokerCreator implements Creator {

		@Override
		public CommandInvoker create() {

			Map<String, Object> params = tuple.getObject(0);
			String truelyDatasource = tuple.getObject(3);
			Resource resource = metadata.getResource();
			Class<?> returnType = metadata.getReturnType();

			if (!isPrimaryOrMappedWrapTypeOrFrequentType(returnType) && !isRecordForTruelyReturnType(returnType)) {
				return new BeanQueryOneCommandInvoker(truelyDatasource, resource.sql(), params, returnType,
						resource.nativeSql(), resource.useCache());
			}

			return null;
		}

	}
}
